﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Visuals;
using System.Drawing;
using Silvermoon.Utils;
using Silvermoon.Images;
using Silvermoon.Animations;
using Silvermoon.OpenGL;

namespace Silvermoon.Controls
{
    public class ProgressBar : NotifyEventControl, IScrollEventBehavior
    {
        const int borderSize = 2;
        const int ChangeDuration = 330;
        #region fields

        private Color color;
        private Animation animation;
        private Point downPoint;
        private BackgroundVisual background;
        private BackgroundVisual progressVisual;
        private StringVisual stringVisual;
        private int value;
        private bool displayValue;
        private int minimum;
        private int maximum;

        #endregion
        #region ctor

        public ProgressBar()
            : base()
        {
            Color = Color.Green;
            ReadOnly = true;
            maximum = 100;
            background = new BackgroundVisual { Colors = ColorUtil.CreateFlatChromeColors(Color.Silver), Style = RenderStyle.Chrome, Texture = SysImages.ProgressBorder };
            progressVisual = new BackgroundVisual { Colors = ColorUtil.CreateFlatChromeColors(Color), Style = RenderStyle.Chrome };
            Visuals = new Visual[] { background, progressVisual };
            Size = new Size(100, 48);
        }

        public override void Dispose()
        {
            AbortAnimation();
            if (stringVisual != null && displayValue)
            {
                stringVisual.Dispose();
                stringVisual = null;
            }
            base.Dispose();
        }

        #endregion
        #region props

        /// <summary>
        /// Gets or sets the color for the progress bar.
        /// </summary>
        public Color Color
        {
            get { return color; }
            set { if (color != value) { color = value; Invalidate(InvalidationFlag.Color); } }
        }

        /// <summary>
        /// Gets or sets whether to display the value as text inside the progress bar. The default value is false.
        /// </summary>
        public bool DisplayValue
        {
            get { return displayValue; }
            set
            {
                if (displayValue != value)
                {
                    displayValue = value;
                    Invalidate(InvalidationFlag.Appearance | InvalidationFlag.Custom);
                }
            }
        }

        /// <summary>
        /// Gets or sets whether the progress bar is readonly or whether it can be modified with tapping or wiping. The default value is true.
        /// </summary>
        public bool ReadOnly { get; set; }

        /// <summary>
        /// Gets or sets the actual value which must be between Miniumum and Maximum.
        /// </summary>
        public int Value { get { return value; } set { value = ValidateValue(value); if (this.value != value) { AbortAnimation(); OnValueChange(this.value, value); } } }

        /// <summary>
        /// Gets or sets the minimum value.
        /// </summary>
        public int Minimum { get { return minimum; } set { if (value != minimum) { minimum = value; Value = ValidateValue(this.value); Invalidate(InvalidationFlag.Appearance); } } }

        /// <summary>
        /// Gets or sets the maximum value. The default value is 100.
        /// </summary>
        public int Maximum { get { return maximum; } set { if (value != maximum) { maximum = value; Value = ValidateValue(this.value); Invalidate(InvalidationFlag.Appearance); } } }

        #endregion
        #region methods

        protected override void PreLayout()
        {
            base.PreLayout();
            Rectangle r = new Rectangle(0, 0, Width, Height);
            r.Inflate(-borderSize, -borderSize);
            ClientBounds = r;
        }

        protected virtual void OnValueChange(int oldValue, int newValue)
        {
            this.value = newValue;
            
            Invalidate(InvalidationFlag.Appearance);

            if (ValueChanged != null) ValueChanged(this, EventArgs.Empty);
        }

        private int ValidateValue(int value)
        {
            if (value < minimum) return minimum;
            if (value > maximum) return maximum;
            return value;
        }

        protected override void OnLayout()
        {
            if (CheckFlag(InvalidationFlag.Color))
            {
                progressVisual.Colors = ColorUtil.CreateFlatChromeColors(Color);
            }
            if (CheckFlag(InvalidationFlag.Custom))
            {
                if (displayValue)
                {
                    if (stringVisual == null) stringVisual = new StringVisual
                    {
                        Alignment = Alignment.Center,
                        LineAlignment = Alignment.Center,
                        LineMode = LineMode.SingleLine,
                        Color = Color.White,
                        Shadowed = true
                    };
                    Visuals = new Visual[] { background, progressVisual, stringVisual };
                }
                else
                {
                    Visuals = new Visual[] { background, progressVisual };
                }
            }
            if (CheckFlag(InvalidationFlag.Size | InvalidationFlag.Margin | InvalidationFlag.Appearance))
            {
                Rectangle r = ClientBounds;
                background.Bounds = Control.ScaledRect(0, 0, Width, Height);
                int size = maximum > 0 ? value * r.Width / maximum : 0;
                progressVisual.Visible = size > 0;
                if (size > 0)
                {
                    r.Width = size;
                    progressVisual.Bounds = Control.Scale(r);
                }
                if (displayValue)
                {
                    stringVisual.Bounds = ClientBounds;
                    stringVisual.Text = GetValueText();
                }

            }
            base.OnLayout();
        }

        public override void OnMouseDown(MouseDownEventArgs e)
        {
            if (!e.Handled && !ReadOnly)
            {
                downPoint = e.ScreenToClient(e.Position);
                SetValue(downPoint);
                e.Handled = true;
            }
            base.OnMouseDown(e);
        }

        public override void OnMouseMove(MouseMoveEventArgs e)
        {
            if (!ReadOnly)
            {
                Point p = e.ScreenToClient(e.Position);
                SetValue(p);
            }
            base.OnMouseMove(e);
        }

        private void SetValue(Point p)
        {
            int offset = p.X - ClientLeft;
            int w = Math.Max(1, ClientWidth);
            if (offset < 0) offset = 0;
            else if (offset > w) offset = w;
            this.Value = minimum + ((maximum - minimum) * offset / w);
        }

        protected virtual void AbortAnimation()
        {
            if (animation != null) animation.Abort();
        }

        /// <summary>
        /// Changes the value in a transition.
        /// </summary>
        /// <param name="value"></param>
        public void ChangeTo(int value)
        {
            value = ValidateValue(value);
            int d = Math.Abs(value - this.value);
            if (d > 4)
            {
                AbortAnimation();
                animation = new Animation(ProgressBar.ChangeDuration, this.value, value, 0);
                animation.Progress += (s, e) => OnValueChange(this.value, e.Value);
                animation.InvokeAsync();
            }
            else Value = value;
        }

        protected virtual string GetValueText()
        {
            string text = value.ToString();
            if (GetDisplayText != null)
            {
                ProgressBarTextEventArgs e = new ProgressBarTextEventArgs { Text = text };
                GetDisplayText(this, e);
                text = e.Text;
            }
            return text;
        }

        #endregion
        #region events

        public event EventHandler ValueChanged;

        public event EventHandler<ProgressBarTextEventArgs> GetDisplayText;

        #endregion
        #region IScrollEventBehavior Members

        public void OnScroll(ScrollEventArgs e)
        {
            e.Handled = true;
            e.Cancel = true;
        }

        public void OnBeginScrollData(BeginScrollEventArgs e)
        {
            e.Handled = true;
            e.Cancel = true;
        }

        public void OnEndScrollData(EndScrollEventArgs e)
        {
            e.Handled = true;
        }

        #endregion
    }
}
